using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;

namespace WPFTemplateLib.WpfHelpers
{
    /// <summary>
    /// DependencyObject 的扩展类
    /// </summary>
    public static class DependencyObjectExtension
    {
        /// <summary>
        /// WPF中查找元素的父元素
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj">The i_dp.</param>
        /// <returns>T.</returns>
        public static T FindParent<T>(this DependencyObject obj) where T : DependencyObject
        {
            DependencyObject dependencyObject = (DependencyObject)VisualTreeHelper.GetParent(obj);
            if (dependencyObject != null)
            {
                if (dependencyObject is T t)
                {
                    return t;
                }
                else
                {
                    dependencyObject = FindParent<T>(dependencyObject);
                    if (dependencyObject is T t1)
                    {
                        return t1;
                    }
                }
            }
            return null;
        }

        /// <summary>
        /// WPF查找指定类型子元素
        /// </summary>
        /// <typeparam name="T">The type of the child item.</typeparam>
        /// <param name="obj">The object.</param>
        /// <returns>childItem.</returns>
        public static T FindVisualChild<T>(this DependencyObject obj) where T : DependencyObject
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(obj, i);
                if (child is T t)
                    return t;
                else
                {
                    T childOfChild = FindVisualChild<T>(child);
                    if (childOfChild != null)
                        return childOfChild;
                }
            }
            return null;
        }

        /// <summary>
        /// WPF查找所有指定类型子元素.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="depObj">The dep object.</param>
        /// <returns>IEnumerable&lt;T&gt;.</returns>
        public static IEnumerable<T> FindVisualChildren<T>(this DependencyObject depObj) where T : DependencyObject
        {
            if (depObj != null)
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
                    if (child is T t)
                    {
                        yield return t;
                    }

                    foreach (T childOfChild in FindVisualChildren<T>(child))
                    {
                        yield return childOfChild;
                    }
                }
            }
        }

		/// <summary>
		/// Sets the value of the <paramref name="property"/> only if it hasn't been explicitly set.
		/// </summary>
		public static bool SetIfDefault<T>(this DependencyObject o, DependencyProperty property, T value)
		{
			if(DependencyPropertyHelper.GetValueSource(o, property).BaseValueSource == BaseValueSource.Default)
			{
				o.SetValue(property, value);

				return true;
			}

			return false;
		}

		#region Finding an ancestor of a WPF dependency object

		//http://www.hardcodet.net/2008/02/find-wpf-parent

		/// <summary>
		/// Finds a parent of a given item on the visual tree.
		/// </summary>
		/// <typeparam name="T">The type of the queried item.</typeparam>
		/// <param name="child">A direct or indirect child of the queried item.</param>
		/// <returns>The first parent item that matches the submitted
		/// type parameter. If not matching item can be found, a null
		/// reference is being returned.</returns>
		public static T TryFindParent<T>(this DependencyObject child)
			where T : DependencyObject
		{
			//get parent item
			DependencyObject parentObject = GetParentObject(child);

			//we've reached the end of the tree
			if(parentObject == null)
				return null;

			//check if the parent matches the type we're looking for
			T parent = parentObject as T;
			if(parent != null)
			{
				return parent;
			}
			else
			{
				//use recursion to proceed with next level
				return TryFindParent<T>(parentObject);
			}
		}

		/// <summary>
		/// This method is an alternative to WPF's
		/// <see cref="VisualTreeHelper.GetParent"/> method, which also
		/// supports content elements. Keep in mind that for content element,
		/// this method falls back to the logical tree of the element!
		/// </summary>
		/// <param name="child">The item to be processed.</param>
		/// <returns>The submitted item's parent, if available. Otherwise
		/// null.</returns>
		public static DependencyObject GetParentObject(this DependencyObject child)
		{
			if(child == null)
				return null;

			//handle content elements separately
			ContentElement contentElement = child as ContentElement;
			if(contentElement != null)
			{
				DependencyObject parent = ContentOperations.GetParent(contentElement);
				if(parent != null)
					return parent;

				FrameworkContentElement fce = contentElement as FrameworkContentElement;
				return fce != null ? fce.Parent : null;
			}

			//also try searching for parent in framework elements (such as DockPanel, etc)
			FrameworkElement frameworkElement = child as FrameworkElement;
			if(frameworkElement != null)
			{
				DependencyObject parent = frameworkElement.Parent;
				if(parent != null)
					return parent;
			}

			//if it's not a ContentElement/FrameworkElement, rely on VisualTreeHelper
			return VisualTreeHelper.GetParent(child);
		}

		/// <summary>
		/// Tries to locate a given item within the visual tree,
		/// starting with the dependency object at a given position. 
		/// </summary>
		/// <typeparam name="T">The type of the element to be found
		/// on the visual tree of the element at the given location.</typeparam>
		/// <param name="reference">The main element which is used to perform
		/// hit testing.</param>
		/// <param name="point">The position to be evaluated on the origin.</param>
		public static T TryFindFromPoint<T>(UIElement reference, Point point)
		where T : DependencyObject
		{
			DependencyObject element = reference.InputHitTest(point)
				as DependencyObject;
			if(element == null)
				return null;
			else if(element is T)
				return (T)element;
			else
				return TryFindParent<T>(element);
		} 

		#endregion
	}
}
